/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/


/* -------------------------------------------------------------------------- */

#include "nu_about.h"
#include "nu_variant.h"
#include "nu_functions.h"
#include "nu_os_console.h"
#include "nu_os_std.h"
#include "nu_eval_expr.h"
#include "nu_builtin_help.h"
#include "nu_lang_defs.h"
#include "nu_expr_var.h"

#include <functional>
#include <ctime>
#include <string>
#include <cstdlib>
#include <stdlib.h>

/* -------------------------------------------------------------------------- */

namespace nu
{

/* -------------------------------------------------------------------------- */

functions_t* functions_t::_instance = nullptr;


/* -------------------------------------------------------------------------- */

static variant_t evaluate_expression(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args);


/* -------------------------------------------------------------------------- */


#define DEFINE_CAST_TO(_T) \
static variant_t cast_to_ ## _T(\
   rt_prog_ctx_t & ctx,\
   const std::string& name,\
   const nu::func_args_t & args)\
{\
   rt_error_code_t::get_instance().throw_if(\
      args.size() != 1,\
      0,\
      rt_error_code_t::E_INVALID_ARGS,\
      "");\
      \
   auto value = args[0]->eval(ctx);\
   return variant_t(value.to_ ## _T());\
}

DEFINE_CAST_TO(double)
DEFINE_CAST_TO(float)
DEFINE_CAST_TO(int)
DEFINE_CAST_TO(string)


/* -------------------------------------------------------------------------- */

void functions_t::check_arg_num(
   const nu::func_args_t & args,
   int expected_arg_num,
   const std::string & fname)
{
   std::string error =
      "'" + fname +
      "': expects to be passed " +
      nu::to_string(expected_arg_num) + " argument(s)";

   switch (expected_arg_num)
   {
      case 0:
         error =
            "'" + fname +
            "': expects to be passed no arguments";
         break;

      case 1:
         error =
            "'" + fname +
            "': expects to be passed one argument";
         break;
   }

   bool valid_args =
      (expected_arg_num == 0 && args.size() == 0) ||
      (expected_arg_num == 0 && args.size() == 1 && args[0]->empty()) ||
      (expected_arg_num == 1 && args.size() == 1 && !args[0]->empty()) ||
      (expected_arg_num > 1 && int(args.size()) == expected_arg_num);

   rt_error_code_t::get_instance().throw_if(
      !valid_args,
      0,
      rt_error_code_t::E_INVALID_ARGS,
      error);
}



/* -------------------------------------------------------------------------- */

void functions_t::get_functor_vargs(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args,
   const std::vector<variant_t::type_t> & check_vect,
   std::vector< variant_t > & vargs)
{
   functions_t::check_arg_num(args, int(check_vect.size()), name);

   for (auto & arg : args)
   {
      vargs.push_back(arg->eval(ctx));
   }

   int i = 0;

   for (auto & vargt : check_vect)
   {
      bool invalid_check = true;

      if (vargt == variant_t::type_t::UNDEFINED)
      {
         // Do not care about this argument
         continue;
      }

      // Accept implicit conversion from/to types double/float/int
      if (variable_t::is_number(vargt) &&
            variable_t::is_number(vargs[i].get_type()))
      {
         invalid_check = false;
      }

      else
      {
         invalid_check = vargs[i].get_type() != vargt;
      }

      syntax_error_if(
         invalid_check,
         "'" + name +
         "': expects to be passed argument " + nu::to_string(i + 1) +
         " as " + variant_t::get_type_desc(vargt));

      ++i;
   }
}


/* -------------------------------------------------------------------------- */

static variant_t evaluate_expression(
   rt_prog_ctx_t & ctx,
   const std::string& name,
   const nu::func_args_t & args)
{
   std::vector< variant_t::type_t > check_vect = { variant_t::type_t::STRING };
   std::vector< variant_t > vargs;
   functions_t::get_functor_vargs(ctx, name, args, check_vect, vargs);

   auto arg = static_cast<std::string>(vargs[0]);

   return nu::eval_expr(ctx, arg);
}


/* -------------------------------------------------------------------------- */

static variant_t process_operator(
   rt_prog_ctx_t & ctx,
   const std::string& operator_name,
   const nu::func_args_t & args)
{
   rt_error_code_t::get_instance().throw_if(
      args.size() != 1,
      0,
      rt_error_code_t::E_INVALID_ARGS,
      "");


   auto var = args[0];
   auto var_ptr = dynamic_cast<expr_var_t*>(var.get());

   rt_error_code_t::get_instance().throw_if(
      var_ptr == nullptr,
      0,
      rt_error_code_t::E_INVALID_ARGS,
      "");

   const auto variable_name = var_ptr->name();

   // Resolve scope using the most nested one
   var_scope_t::handle_t scope =
      ctx.proc_scope.get(proc_scope_t::type_t::LOCAL);

   auto variable_value = &(*scope)[variable_name];

   if (operator_name == PROGLANG_OP_INC)
   {
      variable_value->increment();
   }

   else if (operator_name == PROGLANG_OP_DEC)
   {
      variable_value->decrement();
   }

   else
   {
      rt_error_code_t::get_instance().throw_if(
         var_ptr == nullptr,
         0,
         rt_error_code_t::E_FUNC_UNDEF,
         "");
   }

   return variable_value;
}


/* -------------------------------------------------------------------------- */

functions_t& functions_t::get_instance()
{
   if (_instance)
      return *_instance;

   _instance = new functions_t();
   assert(_instance);
   functions_t & fmap = *_instance;


   // Functions //-----------------------------------------------------
   auto & hmap = builtin_help_t::get_instance();

   // Execute code
   fmap["eval"] = evaluate_expression;

   hmap[ { lang_item_t::FUNCTION, "eval" }] =
   {
      "Evaluates expression of a given string. "
      "The expression can include existing variables.",
      "variant eval(" PROGLANG_TYPE_STRING " expr)"
   };


   // Built-in unary operators

   fmap[PROGLANG_OP_INC] = process_operator;

   hmap[ { lang_item_t::FUNCTION, PROGLANG_OP_INC }] = //TODO
   {
      "Increment unary operator",
      PROGLANG_OP_INC "variable"
   };

   fmap[PROGLANG_OP_DEC] = process_operator;

   hmap[ { lang_item_t::FUNCTION, PROGLANG_OP_DEC }] = //TODO
   {
      "Decrement unary operator",
      PROGLANG_OP_DEC "variable"
   };


   struct _import_t
   {
      int operator()(const std::string& module_name) throw()
      {
         return module_name == "*" ?
                (module_t::import_all() ? 0 : -1) :
                (module_t::import_module(module_name) ? 0 : -1);
      }
   };

   fmap["import"] = functor_int_string < _import_t > ;

   //------------------------------------------------------------------

   struct _rgb
   {
      int operator()(int r, int g, int b) throw()
      {
         return
            ((int)(((uint8_t)(r) |
                    ((uint16_t)((uint8_t)(g)) << 8)) |
                   (((uint32_t)(uint8_t)(b)) << 16)));
      }
   };


   fmap["rgb"] = functor_int_int_int_int < _rgb > ;


   //------------------------------------------------------------------

   struct _boolean_not
   {
      int operator()(double x) throw()
      {
         return int((x == 0.0F) ? 1 : 0);
      }
   };

   fmap["not"] = functor_int_double < _boolean_not > ;

   hmap[ {lang_item_t::FUNCTION, "not"}] =
   {
      "Returns 1 if x=0, otherwise 0",
      "int not(int x)"
   };


   //------------------------------------------------------------------

   fmap["double"] = cast_to_double;

   hmap[ {lang_item_t::FUNCTION, "double"}] =
   {
      "Converts the given expression into double",
      "double(<expression>)"
   };


   //------------------------------------------------------------------

   fmap["int"] = cast_to_int;

   hmap[ {lang_item_t::FUNCTION, "int"}] =
   {
      "Converts the given expression into integer",
      "int(<expression>)"
   };


   //------------------------------------------------------------------

   fmap["string"] = cast_to_string;

   hmap[ {lang_item_t::FUNCTION, "string"}] =
   {
      "Converts the given expression into string",
      "string(<expression>)"
   };

   //------------------------------------------------------------------

   fmap["float"] = cast_to_float;

   hmap[ {lang_item_t::FUNCTION, "float"}] =
   {
      "Converts the given expression into float",
      "float(<expression>)"
   };



   //------------------------------------------------------------------

   return *_instance;
}


/* -------------------------------------------------------------------------- */

} // nu

